/******************************************************************************
 * $Id: treefunc.c,v 1.122 2004/09/22 01:14:23 jimb Exp $
 * @file
 * @author  Jim Brooks http://www.jimbrooks.org
 * @brief   Functions to display tree views of function relationships.
 * @remarks A signal connection will undo any previous signal connection,
 *          so gtk_signal_disconnect() is unnecessary.
 ******************************************************************************/

#include "common.h"
#include "treefunc.h"

#include "handlers_tree.h"
#include "handlers.h"
#include "funcmisc.h"
#include "funcgraph.h"
#include "widgets.h"
#include "text.h"
#include "font.h"
#include "statusbar.h"

static GHashTable*  pHashSubtrees		= NULL;

#define SIGNAL_MASK_NONE			(1<<8)
#define SIGNAL_MASK_SELECT_CHILD	(1<<9)
#define SIGNAL_MASK_SELECT			(1<<10)
#define SIGNAL_MASK_POSSIBLE		(SIGNAL_MASK_NONE | SIGNAL_MASK_SELECT_CHILD | SIGNAL_MASK_SELECT)

/*****************************************************************************
 * Refresh the functree widget.
 * This is called when the verbosity pref is changed.
 *****************************************************************************/
void
RefreshFuncTree( void )
{
   int	line;

   if ( IsTextWidgetEmpty() ) return;
   line = PrintLineNum();
   UpdateFuncTree( line, TRUE );

   return;
}

/*****************************************************************************
 * Update the functree according to the cursor position.
 * This is invoked by an idle function which is in turn invoked by
 * keypress event handlers.
 * @param   line
 *          Line # of text cursor.
 * @param   force
 *          Force update if possible.
 * @remarks The global pModuleLoaded can't be used here because
 *          a global string search could make it reference another
 *          module that the text widget isn't showing.
 *****************************************************************************/
void
UpdateFuncTree( int      line,
                gboolean force )
{
   static module_t*		pModulePrev			= NULL;
   static int			linePrev			= -1;
   static funcDef_t*	pFuncDefPrev		= NULL;
   static funcDef_t*	pFuncDef			= NULL;

  /*
   * Quit if updating functree was suspended by user (depressing check button),
   * or if user is pressing Left-Shift and Left-Ctrl.
   */
   if ( disableFuncTreeUpdates  ||  treeKeyPressed  ||  skipFuncTree )
      return;

  /*
   * Avoid nesting.
   */
   if ( busy_UpdateFuncTree  ||  IsAnyTreeFunctionBusy()  ||  IsAnyMajorFunctionBusy(0) )
      return;
   busy_UpdateFuncTree = TRUE;

  /*
   * Do nothing if the module and/or text widget appears empty.
   * Or line is invalid (-1).
   */
   if (    line < 0
        || ActiveModule() == NULL
#if 0
        || ActiveModule()->pModuleBuffer == NULL /* ignore, might change to NULL by global searches */
#endif
        || IsTextWidgetEmpty() )
      goto idle_quit;

  /*
   * Do nothing if the cursor is at the same spot (same module/line).
   */
   if (     !force
        &&  line == linePrev
        &&  ActiveModule() == pModulePrev )
      goto idle_quit;

   linePrev = line;
   pModulePrev = ActiveModule();

  /*
   * Search the list of funcDef_t structs to determine if the line
   * the cursor is on is within a function definition.
   */
   pFuncDef = SearchFuncDefList( line );

  /*
   * Is the cursor at the same function definition?
   */
   if ( !force  &&  pFuncDef == pFuncDefPrev )
     goto idle_quit;

   pFuncDefPrev = pFuncDef;

  /*
   * Actually update the tree widget.
   */
   UpdateFuncTreeWidget( pFuncDef );

idle_quit:
   busy_UpdateFuncTree = FALSE;
   return;
}

/*****************************************************************************
 * Schedule an update of the functree.
 * The purpose of this is for situations in which UpdateFuncTree()
 * would do nothing when other functions are busy.
 *****************************************************************************/
void
ScheduleUpdateFuncTree( void )
{
  /*
   * Reuse IdleLineNum().
   */
   gtk_idle_add( IdleLineNum, NULL );
}

/*****************************************************************************
 * If all items of a tree are removed while an item is selected, then
 * a non-fatal assertion in GTK+ sometimes fails when another item is selected.
 * So, as a KLUDGE, ensure no item is selected.
 * Gtk-CRITICAL **: file gtkwidget.c: line 3246 (gtk_widget_set_state): assertion `widget != NULL' failed.
 *****************************************************************************/
static void
KLUDGE_EnsureNoTreeItemIsSelected( GtkTree* pTree )
{
   GtkTreeItem* pTreeItem0;
g_return_if_fail( pTree );

  /*
   * Get ptr to tree's first item.
   */
if ( !pTree->children ) return;
   pTreeItem0 = (GtkTreeItem*) pTree->children->data;
if ( !pTreeItem0 ) return;

  /*
   * First disconnect any signals (otherwise, hypersrc's handler will print BUSY),
   * then select/unselect first tree item.
   */
//   gtk_signal_handlers_destroy( GTK_OBJECT(pTreeItem0) );//lv
   gtk_tree_select_child(   pTree, GTK_WIDGET(pTreeItem0) );
   gtk_tree_unselect_child( pTree, GTK_WIDGET(pTreeItem0) );
}

/*****************************************************************************
 * Create/update the tree widget that depicts a function definition.
 * @param   pFuncDef
 *          Pointer to a funcDef_t struct of the function definition to depict.
 *          If NULL then the tree widget will be created (or cleared) empty.
 * @param   pWidgetFuncTreeRoot (OUT/GLOBAL)
 *          The root tree.
 * @remarks Even if a func def is a skeleton (it doesn't call and isn't called),
 *          an empty root tree with its name is still shown, rather than nothing.
 *          This makes sense since if you move thru a text widget while looking at
 *          the tree widget, the tree should indicate which func def the cursor is on.
 *          If no tree at all is shown, then that means the parser missed a func def.
 *****************************************************************************/
void
UpdateFuncTreeWidget( funcDef_t* pFuncDef )
{
   static GtkTree*		pTreeCalls				= NULL;
   static GtkTree*		pTreeCalledBy			= NULL;
   static char			str[MAX_TAG_NAME_LEN];
          char*		    pFuncDefName			= NULL;

  /*
   * Quit if function tree isn't supposed to be built ever (eg --no-functree).
   */
   if ( skipFuncTree ) return;

  /*
   * Create or clear the root tree.
   */
   if ( pWidgetFuncTreeRoot == NULL )
   {
      pWidgetFuncTreeRoot = gtk_tree_new();
      ChangeStyleTree( GTK_TREE(pWidgetFuncTreeRoot) );
      gtk_widget_show( pWidgetFuncTreeRoot );
   }
   else
   {
     /*
      * First remove all subtrees, then the root-level items.
      */
      KLUDGE_EnsureNoTreeItemIsSelected( GTK_TREE(pWidgetFuncTreeRoot) );
      if ( pTreeCalls )
      {
         gtk_tree_item_remove_subtree( TreeOwnerItem(pTreeCalls) );
         pTreeCalls = NULL;
      }
      if ( pTreeCalledBy )
      {
         gtk_tree_item_remove_subtree( TreeOwnerItem(pTreeCalledBy) );
         pTreeCalledBy = NULL;
      }
      gtk_tree_remove_items( GTK_TREE(pWidgetFuncTreeRoot),
                             GTK_TREE(pWidgetFuncTreeRoot)->children );
   }

  /*
   * Stop if no function definition was passed.
   */
   if ( pFuncDef == NULL ) goto quit;

g_return_if_fail( pFuncDef->pTagFuncDef );
   pFuncDefName = pFuncDef->pTagFuncDef->pName;
g_return_if_fail( pFuncDefName );

 /*
   * Create the "foo() called by:" subtree.
   */
   sprintf( str, _("%s{} is called by:"),  pFuncDefName );
   pTreeCalledBy = CreateTreeForFunc( GTK_TREE(pWidgetFuncTreeRoot),
                                      str,
                                      SIGNAL_MASK_SELECT | SIGNAL_MASK_SELECT_CHILD,
                                      pFuncDef->pTagFuncDef );

  /*
   * Fill the "called by" subtree.
   */
   FillTreeWithFuncCallers( pTreeCalledBy, pFuncDef );
   ExpandTree( pTreeCalledBy );

  /*
   * Create a separator item on the first-level (for readability).
   */
   CreateTreeItem( GTK_TREE(pWidgetFuncTreeRoot), "------------------------------------", fontNameTreeModule );

  /*
   * Create the "foo() calls:" subtree.
   */

   sprintf( str, _("%s{} calls:"),  pFuncDefName );

   pTreeCalls = CreateTreeForFunc( GTK_TREE(pWidgetFuncTreeRoot),
                                   str,
                                   SIGNAL_MASK_SELECT | SIGNAL_MASK_SELECT_CHILD,
                                   pFuncDef->pTagFuncDef );
g_return_if_fail( pTreeCalls );

  /*
   * Fill the "foo() calls:" subtree.
   */
   FillTreeWithFuncCalls( pTreeCalls, pFuncDef );
   ExpandTree( pTreeCalls );

  /*
   * Create the ancillary widgets (if necessary).
   */
quit:
   if ( !pWidgetFrameFuncTree )
      CreateFuncTreeAncillaryWidgets();

   return;
}

/*****************************************************************************
 * Fill an empty tree with nodes depicting the function calls made by
 * one function definition.
 *****************************************************************************/
void
FillTreeWithFuncCalls( GtkTree*   pTree,
                       funcDef_t* pFuncDef )
{
   GSList*		pFuncCallList	= NULL;

#ifdef HYPERSRC_PEDANTIC
g_return_if_fail( pTree  &&  pFuncDef  &&  TreeChildrenCount(pTree) == 0 );
#endif

  /*
   * Create a node for every function call made by this function definition
   * (for each funcCall_t in the list pointed to by funcDef_t).
   */
   for ( pFuncCallList = pFuncDef->pGslistFuncCall;
         pFuncCallList;
         pFuncCallList = pFuncCallList->next )
   {
      funcCall_t*	pFuncCall;
      char*			pFuncCallName;

     /*
      * Get info about this func call.
      */
      pFuncCall     = pFuncCallList->data;				/* pointer to funcCall_t */
g_return_if_fail( pFuncCall  &&  pFuncCall->pTagFuncCall );
      pFuncCallName = pFuncCall->pTagFuncCall->pName;	/* name of function being called */
g_return_if_fail( pFuncCallName );

      if ( IgnoreFuncName( pFuncCallName, strlen(pFuncCallName) ) ) continue;

     /*
      * To depict this function call, grow a node if the caller itself
      * calls other functions, otherwise grow a leaf.
      */
      if ( IsModuleOfFuncKnown( pFuncCall->pTagFuncCall ) )
      {
         funcDef_t* pFuncDefCaller = FindFuncDefByItsTag( pFuncCall->pTagFuncCall );
         if ( pFuncDefCaller  &&  pFuncDefCaller->pGslistFuncCall )
         {
            CreateTreeNodeForFunc( pTree, pFuncCall->pTagFuncCall, NULL, DO_ANNOTATE_MODULE, fontNameTreeFuncCall );
            continue;
         }
      }
      CreateTreeItemForFunc( pTree, pFuncCall->pTagFuncCall, NULL, DO_ANNOTATE_MODULE, fontNameTreeFuncCall );
   }

   return;
}

/*****************************************************************************
 * Fill an empty tree with nodes depicting other functions that call one function.
 *****************************************************************************/
void
FillTreeWithFuncCallers( GtkTree*   pTree,
                         funcDef_t* pFuncDefCalled )
{
   GList*		pLinkModuleName			= NULL;
   GSList*		pFuncCallList			= NULL;
   GSList*		pFuncDefList			= NULL;
   char*		pFuncDefCalledName		= NULL;

#ifdef HYPERSRC_PEDANTIC
g_return_if_fail( pTree  &&  TreeChildrenCount(pTree) == 0 );
g_return_if_fail( pFuncDefCalled  && pFuncDefCalled->pTagFuncDef  &&  pFuncDefCalled->pTagFuncDef->pName );
#endif

   pFuncDefCalledName = pFuncDefCalled->pTagFuncDef->pName;

  /*
   * For each module.
   */
   for ( pLinkModuleName = pGlistModuleNames;
         pLinkModuleName;
         pLinkModuleName = pLinkModuleName->next )
   {
      module_t* pModuleTmp;

     /*
      * Get list of funcDef_t for this module.
      */
      pModuleTmp = (module_t*) g_hash_table_lookup( pHashModules, pLinkModuleName->data );
g_return_if_fail( pModuleTmp );

     /*
      * For each function definition (for each funcDef_t of this module).
      */
      for ( pFuncDefList = pModuleTmp->pGslistFuncDef;
            pFuncDefList;
            pFuncDefList = pFuncDefList->next )
      {
         funcDef_t*	pFuncDefThis = pFuncDefList->data;
g_return_if_fail( pFuncDefThis );

        /*
         * For each function call made by this function definition (for each funcCall_t).
         */
         for ( pFuncCallList = pFuncDefThis->pGslistFuncCall;
               pFuncCallList;
               pFuncCallList = pFuncCallList->next )
         {
            funcCall_t* pFuncCall;
            tag_t*		pTagCaller;

            pFuncCall  = pFuncCallList->data;
g_return_if_fail( pFuncCall );
            pTagCaller = pFuncDefThis->pTagFuncDef;
g_return_if_fail( pTagCaller );

           /*
            * Is this function call calling the function definition?
            */
            if ( CompareStrings( pFuncDefCalledName, pFuncCall->pTagFuncCall->pName ) )
               continue;

           /*
            * To reduce verbosity, avoid displaying multiple calls by the SAME tagged function.
            * However, do show each call from same-named functions in different modules.
            */
            if ( FilterDuplicateFuncCalls( pFuncDefThis->pGslistFuncCall, pFuncCall ) )
               continue;

           /*
            * Grow a node to depict this caller.
            */
            CreateTreeNodeForFunc( pTree, pTagCaller, NULL, DO_ANNOTATE_MODULE, fontNameTreeFuncCall );
         }         
      }
   }

   return;
}

/*****************************************************************************
 * Create the functree's ancillary widgets.
 * Later, the functree's frame will be added to a paned widget (along with the notebook).
 * @param   pWidgetFrameFuncTree (OUT/GLOBAL)
 * @param   pWidgetScrolledFuncTree (OUT/GLOBAL)
 * @param   pWidgetCheckButtonFunctree (OUT/GLOBAL)
 *****************************************************************************/
void
CreateFuncTreeAncillaryWidgets( void )
{
   GtkWidget*	pWidgetLabel	= NULL;
   GtkWidget*	pWidgetHbox		= NULL;
   GtkWidget*	pWidgetVbox		= NULL;

#undef  SPACING 
#define SPACING		1
#undef  PADDING
#define PADDING		1

  /*
   * Create outer-level vbox.
   */
   pWidgetVbox = gtk_vbox_new( FALSE, SPACING );

  /*
   * Create a scrolled widget for the functree.
   */
   pWidgetScrolledFuncTree = gtk_scrolled_window_new( NULL, NULL );
   gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW(pWidgetScrolledFuncTree),
                                          pWidgetFuncTreeRoot );

  /*
   * Pack scrolled widget into vbox.
   */
   gtk_box_pack_start( GTK_BOX(pWidgetVbox),
                       pWidgetScrolledFuncTree,
                       TRUE, TRUE, PADDING );
  /*
   * Create check button to disable functree, pack it into vbox.
   */
   pWidgetHbox = gtk_hbox_new( FALSE, SPACING );				/* create hbox */

   pWidgetLabel = gtk_label_new( "             " );
   gtk_box_pack_start( GTK_BOX(pWidgetHbox),					/* pack label in hbox */
                       pWidgetLabel,
                       FALSE, FALSE, PADDING );

   pWidgetCheckButtonFunctree = gtk_check_button_new();			/* create check button */
   gtk_box_pack_start( GTK_BOX(pWidgetHbox),					/* pack check button in hbox */
                       pWidgetCheckButtonFunctree,
                       FALSE, FALSE, PADDING );

   pWidgetLabel = gtk_label_new( _("stop updating this tree") );
   gtk_box_pack_start( GTK_BOX(pWidgetHbox),					/* pack label in hbox */
                       pWidgetLabel,
                       FALSE, FALSE, PADDING );

   gtk_box_pack_start( GTK_BOX(pWidgetVbox),					/* pack hbox in vbox */
                       pWidgetHbox,
                       FALSE, FALSE, PADDING );

   gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(pWidgetCheckButtonFunctree),
                                 FALSE );						/* before connecting signal! */
   gtk_signal_connect( GTK_OBJECT(pWidgetCheckButtonFunctree),	/* connect toggle signal */
                       "toggled",
                       GTK_SIGNAL_FUNC(HandlerButtons),
                       NULL );

  /*
   * Finally, add the vbox into the functree's frame.
   */
   pWidgetFrameFuncTree = gtk_frame_new( NULL );
   gtk_container_add( GTK_CONTAINER(pWidgetFrameFuncTree),		/* pack vbox in frame */
                      pWidgetVbox );

  /*
   * Install focus handlers.
   */
   InstallFocusHandlers( pWidgetScrolledFuncTree );

   return;
}

/*****************************************************************************
 * Refresh the tree view of modules.
 *****************************************************************************/
void
RefreshModuleTree( void )
{
  /*
   * This is a wrapper for:
   */
   CreateNotebookPageWithModuleTree( DO_DECORATE );
}

/*****************************************************************************
 * [Re]Create the notebook page that holds the tree views of modules and
 * their functions.  Also create the "module tree".  In the module tree,
 * modules are sorted by name, while functions in a module are sorted by line.
 * Later, whenever the cursor is moved over a particular function, a second tree
 * ("func tree") will be created below the module tree for that particular function.
 *****************************************************************************/
void
CreateNotebookPageWithModuleTree( gboolean decorate )
{
   GtkWidget*	pWidgetLabel;
   GList*		pLinkModuleName;
   gboolean     recreate;

   if ( skipFuncTree ) return;

#ifdef HYPERSRC_PEDANTIC
g_assert( pHashModules  &&  pWidgetNotebook );
#endif

   recreate = pWidgetModuleTree != NULL;

  /*
   * Create a hash to remember module pathnames associated with subtrees.
   */
   pHashSubtrees = g_hash_table_new( g_str_hash, g_str_equal );
g_return_if_fail( pHashSubtrees );

  /*
   * [Re]create the module tree.
   */
   if ( recreate )
   {
     /*
      * This wastes lots of memory on GTK+ 1.2.8 when browsing lots of files.
      * Evidently, some memory isn't freed by destroying a tree or removing its items.
      * gtk_tree_remove/clear() has the same problem too.
      */
      KLUDGE_EnsureNoTreeItemIsSelected( GTK_TREE(pWidgetModuleTree) );
      gtk_widget_destroy( pWidgetModuleTree );
   }
   pWidgetModuleTree = gtk_tree_new();
g_return_if_fail( pWidgetModuleTree );
   ChangeStyleTree( GTK_TREE(pWidgetModuleTree) );

  /*
   * For each module (using sorted list of module names).
   */
   for ( pLinkModuleName = pGlistModuleNames;
         pLinkModuleName  &&  decorate;
         pLinkModuleName = pLinkModuleName->next )
   {
      module_t* pModuleTmp;

      pModuleTmp = (module_t*) g_hash_table_lookup( pHashModules, pLinkModuleName->data );
g_return_if_fail( pModuleTmp );
      ModuleTreeForEachModule( pLinkModuleName->data, pModuleTmp );
   }

  /*
   * Add the module tree to the notebook.
   */
   if ( recreate )
   {
     /*
      * The scrolled widget is now containing nothing since
      * the module tree was destroyed earlier, so add the new one.
      */
      gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW(pWidgetScrolledModuleTree),
                                             pWidgetModuleTree );
      gtk_widget_show( pWidgetModuleTree );
   }
   else
   {
     /*
      * Create a scrolled widget to contain the module tree.
      */
      pWidgetScrolledModuleTree = gtk_scrolled_window_new( NULL, NULL );
      gtk_scrolled_window_add_with_viewport( GTK_SCROLLED_WINDOW(pWidgetScrolledModuleTree),
                                             pWidgetModuleTree );

     /*
      * Create the "functions" notebook page.
      */
      //pWidgetLabel = gtk_label_new(_( "functions" ));
      GtkWidget *pImage = gtk_image_new_from_file(IMAGE_DIR"function.gif");
      gtk_notebook_append_page( GTK_NOTEBOOK(pWidgetNotebook),
                                pWidgetScrolledModuleTree,
                                pImage );

      gtk_widget_show( pWidgetModuleTree );
      gtk_widget_show( pWidgetScrolledModuleTree );
   }

  /*
   * Free subtree hash.
   */
   g_hash_table_foreach( pHashSubtrees, FreeHashSubtreeItem, NULL ); /* free each dir string */
   g_hash_table_destroy( pHashSubtrees );
   pHashSubtrees = NULL;

   return;
}

/*****************************************************************************
 * For one module, grow a branch in the module tree corresponding to
 * the module's pathname.  Leafs for the module's function definitions
 * will be grown only when the module's node is expanded by the user
 * (which saves lots of memory).
 *****************************************************************************/
void
ModuleTreeForEachModule( char*     pModulePathname,
                         module_t* pModule )
{
   GtkTree*				pTreeFilename			= NULL;		/* parent tree of subtree labeled with module's filename */
   GtkTreeItem*			pTreeItem				= NULL;
   GtkTree*				pSubtree				= NULL;

#ifdef HYPERSRC_PEDANTIC
g_return_if_fail( pModulePathname && pModule );
#endif

  /*
   * Create a tree item labeled with this module's filename.
   * But first, a node must be created for each directory component
   * in the module's pathname.
   */
   pTreeFilename = DescendTreeNodesOnModulePath( pModulePathname );
   pTreeItem = CreateTreeItem( pTreeFilename,
                               ExtractFilenameFromPathname( pModulePathname ),
                               fontNameTreeModule );
g_return_if_fail( pTreeItem );

  /*
   * Create a subtree (to hold leafs with names of functions in this module).
   */
   pSubtree = CreateSubtree( pTreeItem,
                             SIGNAL_MASK_SELECT | SIGNAL_MASK_SELECT_CHILD,
                             "module",
                             pModulePathname );
g_return_if_fail( pSubtree );

  /*
   * Connect to the signal when the module's node is expanded by the user.
   * The node will then be decorated with the module's function definitions.
   */
   gtk_signal_connect_after( GTK_OBJECT( GTK_WIDGET(pTreeItem) ),
                             "expand",
                             (GtkSignalFunc) HandlerModuleTreeExpand,
                             pModule );

   return;
}

/*****************************************************************************
 * Decorate a module's subtree with function definitions.
 *****************************************************************************/
void
ModuleTreeDecorateSubtree( GtkTree*  pSubtree,
                           module_t* pModule )
{
   GSList*				pFuncDefList;
   static gboolean      busy                = FALSE;

g_return_if_fail( pSubtree  &&  pModule );

  /*
   * Avoid nesting (usually this is fast, but it might take a few seconds).
   */
   if ( busy ) { StatusbarBusy(); return; }
   busy = TRUE;

  /*
   * For each function definition (that was tagged) in this module.
   */
   for ( pFuncDefList = pModule->pGslistFuncDef;
         pFuncDefList;
         pFuncDefList = pFuncDefList->next )
   {
      funcDef_t* pFuncDef = (funcDef_t*) pFuncDefList->data;
g_return_if_fail( pFuncDef );

     /*
      * To depict this function definition (in this module),
      * grow a node if it calls other functions, otherwise grow a leaf.
      * Don't annotate either with its module name (would be tautological).
      *
      * A retrofit is that pFuncDef is annotated to the tree object,
      * because funcDef_t contains the more accurate line number.
      * Eg, if a C++ file has multiple constructors such as MyClass(), then,
      * because tags are hashed without chaining (overwritten), the hash
      * will contain only one tag for all of the MyClass constructors.
      * In order to hyperjump to the appropriate line in such cases,
      * the line number in funcDef_t is used instead of tag_t
      * later by HyperjumpViaTreeNodeOrItem_cb() [handlers_tree.c].
      */
      if ( pFuncDef->pGslistFuncCall )
         CreateTreeNodeForFunc( pSubtree, pFuncDef->pTagFuncDef, pFuncDef, DONT_ANNOTATE_MODULE, fontNameTreeFuncDef );
      else
         CreateTreeItemForFunc( pSubtree, pFuncDef->pTagFuncDef, pFuncDef, DONT_ANNOTATE_MODULE, fontNameTreeFuncDef );
   }

   busy = FALSE;
   return;
}

/*****************************************************************************
 * Prior to destroying the subtree hash, free memory used by each hash item.
 *****************************************************************************/
void
FreeHashSubtreeItem( gpointer key,
                     gpointer value,
                     gpointer unused )
{
  /*
   * Free the hash key's string which was created by CloneString().
   */
   DeleteString( key );
}

/*****************************************************************************
 * For the module tree, descend thru the tree nodes that correspond to each
 * directory on the path to a module, creating any node if necessary.
 *
 * The caller is expected to create a subtree for the module's filename,
 * and populate it with function names.
 *
 * @param   pModulePathname
 * @param   pWidgetModuleTree (GLOBAL)
 *          Affected, must be created prior.
 * @param   pHashSubtrees (GLOBAL)
 *          Affected, must be created prior.
 * @return  A pointer to the tree that can be the parent of the subtree
 *          that would contain the module's filename.
 *          The returned tree is either the root or of the last dir part.
 *****************************************************************************/
GtkTree*
DescendTreeNodesOnModulePath( char* pModulePathname )
{
   char*		pNextDir;				/* tentatively points to next dir part */
   char*		pDirBegin;
   char*		pDirEnd;
   int			len;
   char			key[MAX_STRING_LEN];
   char			dir[MAX_STRING_LEN];
   int			iteration;
   GtkTree*		pSubtreeHashed	= NULL;
   GtkTree*		pTreeParent		= NULL;
   GtkTreeItem* pTreeItem		= NULL;
   GtkTree*		pSubtree		= NULL;

#ifdef HYPERSRC_PEDANTIC
g_return_val_if_fail( pModulePathname  &&  pWidgetModuleTree  &&  pHashSubtrees, NULL );
#endif

   pTreeParent = GTK_TREE(pWidgetModuleTree);

  /*
   * For each dir in the module's pathname.
   */
   pNextDir = pModulePathname;
   for ( iteration = 0;
         ;
         ++iteration )
   {
     /*
      * Try to extract the next directory part from the module's pathname.
      */
      ExtractDirFromPathname( pNextDir,
                              &pDirBegin,
                              &pDirEnd );

     /*
      * Break if no dir part was extracted (ie remainder is a filename).
      */
      if ( pDirBegin == NULL ) break;

     /*
      * Tentatively update the pointer to the next dir part.
      */
      pNextDir = pDirEnd + 1;

     /*
      * Has a subtree been created that corresponds
      * to the path that has so far been extracted?
      * If so then continue, else create a subtree.
      */
      len = pDirEnd - pModulePathname + 1;
#ifdef HYPERSRC_PEDANTIC
g_return_val_if_fail( len > 0, NULL );
#endif
      memcpy( key, pModulePathname, len );
      key[len] = '\0';
      pSubtreeHashed = (GtkTree*) g_hash_table_lookup( pHashSubtrees, key );

      if ( pSubtreeHashed )
      {
        /*
         * Update pointer to parent tree for next iteration.
         */
         pTreeParent = (GtkTree*) pSubtreeHashed;	/* hash data is pointer to subtree */
         continue;
      }

     /*
      * Create a subtree whose label is the extracted dir part of the module's pathname.
      */
      len = pDirEnd - pDirBegin + 1;		/* stringify the dir part */
#ifdef HYPERSRC_PEDANTIC
g_return_val_if_fail( len > 0, NULL );
#endif
      memcpy( dir, pDirBegin, len );
      dir[len]   = '/';						/* append '/' to show it is a dir, not a src file */
      dir[len+1] = '\0';

      pTreeItem = CreateTreeItem( pTreeParent, dir, fontNameTreeModule );
      pSubtree = CreateSubtree( pTreeItem,
                                SIGNAL_MASK_NONE,
                                NULL,
                                NULL );

     /*
      * Remember that this subtree was created by inserting
      * the extracted path into a hash.
      */
      g_hash_table_insert( pHashSubtrees,
#if 0
                           (gpointer) key,	/* <-- ### WRONG: key's string ephemerally resides on local stack. */
#else                                       /*     ### Must explicitly allocate key's string. */
                           (gpointer) CloneString(key),
#endif
                           (gpointer) pSubtree );

     /*
      * Update the parent tree pointer, and reiterate.
      */
      pTreeParent = pSubtree;
   }

  /*
   * Return the last update to the parent tree's pointer.
   * If the pathname had no dirs then this will be the true root tree.
   */
   return pTreeParent;
}

/*****************************************************************************
 * Create an empty subtree to depict a function.
 *****************************************************************************/
GtkTree*
CreateTreeForFunc( GtkTree* pTreeParent,
                   char*    pTreeName,
                   guint    signalMask,
                   tag_t*   pTag )
{
   GtkWidget*	pWidgetItem		= NULL;

#ifdef HYPERSRC_PEDANTIC
g_return_val_if_fail( pTreeParent  &&  pTreeName  &&  pTag, NULL );
#endif
#ifdef HYPERSRC_DEBUG
g_assert( signalMask & SIGNAL_MASK_POSSIBLE );
#endif

  /*
   * First, add a "tree item" to the parent tree.
   */
   pWidgetItem = gtk_tree_item_new_with_label( pTreeName );
   ChangeStyleTreeItem( GTK_TREE_ITEM(pWidgetItem), fontNameTreeFuncDef );
   gtk_tree_append( pTreeParent, pWidgetItem );
   gtk_widget_show( pWidgetItem );

  /*
   * Defer the rest to CreateSubtree().
   */
   return CreateSubtree( GTK_TREE_ITEM(pWidgetItem),
                         signalMask,
                         "ptag",
                         pTag );
}

/*****************************************************************************
 * Primitive to create a tree node to depict a function.
 * @param   pTree
 *          Parent tree that will contain the node.
 * @param   pTag
 *          Tag of function.
 * @param   pFuncDef
 *          Function definition.  NULL can be passed if N/A.
 * @param   annotateModule
 *          Whether to annotate node with module name referenced by pTag.
 * @param   pFontName
 *          If NULL then font won't be changed (use default font).
 *****************************************************************************/
void
CreateTreeNodeForFunc( GtkTree*   pTree,
                       tag_t*     pTag,
                       funcDef_t* pFuncDef,
                       int        annotateModule,
                       char*      pFontName )
{
   GtkTreeItem*	pTreeItem;

g_return_if_fail( pTree  &&  pTag );

   pTreeItem = CreateTreeItemForFunc( pTree, pTag, pFuncDef, annotateModule, pFontName );
g_return_if_fail( pTreeItem );
   CreateSubtree( pTreeItem,
                  SIGNAL_MASK_SELECT | SIGNAL_MASK_SELECT_CHILD,
                  "ptag",
                  pTag );

   gtk_signal_connect_after( GTK_OBJECT( GTK_WIDGET(pTreeItem) ),
                             "expand",
                             (GtkSignalFunc) HandlerFuncDefTreeExpand,
                             pTag );
   return;
}

/*****************************************************************************
 * Primitive to create a tree item (leaf) to depict a function.
 * Associate the function's tag_t with the leaf.
 * @param   pTree
 *          Parent tree that will contain the item.
 * @param   pTag
 *          Tag of function.
 * @param   pFuncDef
 *          Function definition.  NULL can be passed if N/A.
 * @param   annotateModule
 *          Whether to annotate item with module name referenced by pTag.
 * @param   pFontName
 *          If NULL then font won't be changed (use default font).
 *****************************************************************************/
GtkTreeItem*
CreateTreeItemForFunc( GtkTree*   pTree,
                       tag_t*     pTag,
                       funcDef_t* pFuncDef,
                       int        annotateModule,
                       char*      pFontName )
{
   GtkTreeItem* pTreeItem;
   static char	str[MAX_TAG_NAME_LEN];

g_return_val_if_fail( pTree  &&  pTag, NULL );

   if ( !annotateModule  ||  !funcTreeVerbose )
      sprintf( str, "%s()", pTag->pName );
   else
   {
      if ( IsStringEmpty(pTag->pModuleName)  ||  !IsModuleOfFuncKnown(pTag) )
         sprintf( str, "%s()  []",   pTag->pName ); /* to indicate module isn't known */
      else
         sprintf( str, "%s()  [%s]", pTag->pName, pTag->pModuleName );
   }
   
   pTreeItem = CreateTreeItem( pTree, str, pFontName );
g_return_val_if_fail( pTreeItem, NULL );

   if ( pTag )     gtk_object_set_data( GTK_OBJECT(GTK_WIDGET(pTreeItem)), "ptag", pTag );
   if ( pFuncDef ) gtk_object_set_data( GTK_OBJECT(GTK_WIDGET(pTreeItem)), "pfd",  pFuncDef );

   return pTreeItem;
}

/*****************************************************************************
 * Generic primitive to create a tree item (leaf).
 * @param   pTree
 *          Parent tree that will contain the item.
 * @param   pItemName
 * @param   pFontName
 *          If NULL then font won't be changed (use default font).
 *****************************************************************************/
GtkTreeItem*
CreateTreeItem( GtkTree* pTree,
                char*    pItemName,
                char*    pFontName )
{
   GtkWidget* pWidgetItem;

g_return_val_if_fail( pTree  &&  pItemName, NULL );

   pWidgetItem = gtk_tree_item_new_with_label( pItemName );
   ChangeStyleTreeItem( GTK_TREE_ITEM(pWidgetItem), pFontName );
   gtk_tree_append( pTree, pWidgetItem );
   gtk_widget_show( pWidgetItem );

   return GTK_TREE_ITEM(pWidgetItem);
}

/*****************************************************************************
 * Primitive to create a subtree (node).
 * @param   pTreeItem
 *          Pointer to tree item to be transformed as a node.
 * @param   signalMask
 *          Which signals to connect.
 * @param   pKey, pData
 *          Object data to be attached to the subtree for use by a handler.
 *          (Note: the object data must be attached to its tree item form).
 *****************************************************************************/
GtkTree*
CreateSubtree( GtkTreeItem* pTreeItem,
               guint        signalMask,
               char*        pKey,
               void*        pData )
{
   GtkWidget*	pWidgetSubtree	= NULL;

g_return_val_if_fail( pTreeItem, NULL );

#ifdef HYPERSRC_DEBUG
g_assert( signalMask & SIGNAL_MASK_POSSIBLE );
#endif

  /*
   * Create a subtree.
   */
   pWidgetSubtree = gtk_tree_new();
g_return_val_if_fail( pWidgetSubtree, NULL );
   ChangeStyleTree( GTK_TREE(pWidgetSubtree) );
   gtk_tree_item_set_subtree( pTreeItem, pWidgetSubtree );

  /*
   * Attach object data (if passed).
   * (Note: attaching data to pWidgetSubtree didn't work).
   */
   if ( pKey  &&  pData )
      gtk_object_set_data( GTK_OBJECT(GTK_WIDGET(pTreeItem)),
                           pKey,
                           pData );

  /*
   * If requested, connect to signals.
   */
   if ( signalMask & SIGNAL_MASK_SELECT_CHILD )
   {
     /*
      * For when an item of the subtree is selected.
      * Connect to the GtkTree "select-child" signal.
      */
      gtk_signal_connect_after( GTK_OBJECT(pWidgetSubtree),	/* yes, pWidgetSubtree */
                                "select-child",
                                (GtkSignalFunc) HandlerTreeItemSelected,
                                NULL );
   }

   if ( signalMask & SIGNAL_MASK_SELECT )
   {

     /*
      * For when the subtree itself is selected (ie the node that can be expanded/collapsed).
      * Connect to the GtkItem "select" signal.
      */
      gtk_signal_connect_after( GTK_OBJECT(GTK_WIDGET(pTreeItem)),	/* yes, pTreeItem */
                                "select",
                                (GtkSignalFunc) HandlerSubtreeSelected,
                                NULL );
   }

   return GTK_TREE(pWidgetSubtree);
}

/*****************************************************************************
 * Change the style of a GtkTree.
 *****************************************************************************/
void
ChangeStyleTree( GtkTree* pTree )
{
   ChangeStyleColorTextSel( GTK_WIDGET(pTree),
                            &colorClistWidgetTextFg,
                            &colorClistWidgetTextBg,
                            &colorClistWidgetSelFg,
                            &colorClistWidgetSelBg );
   /*-- doesn't work: ChangeStyleFont( pWidget, fontNameTree ); */
}

/*****************************************************************************
 * @fn void ChangeStyleTreeItem( GtkTreeItem* pTreeItem, char* pFontName );
 * Change the style of a GtkTreeItem.
 * @param   pTreeItem
 * @param   pFontName
 *          If NULL then font won't be changed (use default font).
 *****************************************************************************/
/** See ChangeStyleTreeItem().  A kludgy way to set the font of tree item since ChangeStyleFont() won't work. */
static void
ChangeStyleTreeItem_cb( GtkWidget* pWidget, gpointer pFontName )
{
   return;
   ChangeStyleFont( pWidget, (char*) pFontName );
}
void
ChangeStyleTreeItem( GtkTreeItem* pTreeItem,
                     char*        pFontName )
{
   ChangeStyleColorTextSel( GTK_WIDGET(pTreeItem),
                            &colorClistWidgetTextFg,
                            &colorClistWidgetTextBg,
                            &colorClistWidgetSelFg,
                            &colorClistWidgetSelBg );
   /*-- doesn't work: ChangeStyleFont( pWidget, fontNameTree ); */
   if ( !IsStringEmpty(pFontName) )
      gtk_container_foreach( GTK_CONTAINER(pTreeItem), ChangeStyleTreeItem_cb, pFontName );
}

/*****************************************************************************
 * Given a base font, tries to assign different variations of the base font
 * to components of the module tree and func tree.
 * If not all of the variations of the base font are available,
 * then the given base font will be used for all components.
 * If the base font itself is unavailable, the GTK+ default font will be used.
 *****************************************************************************/
gboolean
SelectDifferentFontsForFuncTrees( const char* pFontNameBase )
{
   static char fontNameItalic[MAX_FONT_NAME_LEN] = "";
   static char fontNameBold[MAX_FONT_NAME_LEN]   = "";

   if ( StrLen(pFontNameBase) >= MAX_FONT_NAME_LEN )
      return FALSE;

  /*
   * Is base font available?
   */
   if ( !gdk_font_load( pFontNameBase ) )
   {
     /*
      * Empty strings to use GTK+ default font.
      */
      fontNameTreeModule[0]		= 0;
      fontNameTreeFuncDef[0]	= 0;
      fontNameTreeFuncCall[0]	= 0;
      return FALSE;
   }

  /*
   * In case of an unavailable bold/italic font,
   * set the base font for each tree component.
   */

  /*
   * Determine if bold/italic fonts are available.
   */
   if (    FindFontBold(   fontNameBold,   pFontNameBase )
        && FindFontItalic( fontNameItalic, pFontNameBase ) )
   {
     /*
      * modules   : bold
      * func defs : normal
      * func calls: italic
      */
      strncpy( fontNameTreeModule,   fontNameBold,   MAX_FONT_NAME_LEN );
      strncpy( fontNameTreeFuncDef,  pFontNameBase,  MAX_FONT_NAME_LEN );
      strncpy( fontNameTreeFuncCall, fontNameItalic, MAX_FONT_NAME_LEN );
      return TRUE;
   }

  /*
   * Some fonts were unavailable.
   * Use the base font for all tree components.
   */
   strncpy( fontNameTreeModule,   pFontNameBase, MAX_FONT_NAME_LEN );
   strncpy( fontNameTreeFuncDef,  pFontNameBase, MAX_FONT_NAME_LEN );
   strncpy( fontNameTreeFuncCall, pFontNameBase, MAX_FONT_NAME_LEN );
   return FALSE;
}

/*****************************************************************************
 * Use the same base font for tree widgets.
 *****************************************************************************/
void
SelectSameFontForFuncTrees( const char* pFontNameBase )
{
  /*
   * Is base font available?
   */
   if (    StrLen(pFontNameBase) >= MAX_FONT_NAME_LEN
        || !gdk_font_load( pFontNameBase ) )
   {
     /*
      * Empty strings to use GTK+ default font.
      */
      fontNameTreeModule[0]		= 0;
      fontNameTreeFuncDef[0]	= 0;
      fontNameTreeFuncCall[0]	= 0;
      return;
   }

  /*
   * Use the base font for all tree components.
   */
   strncpy( fontNameTreeModule,   pFontNameBase, MAX_FONT_NAME_LEN );
   strncpy( fontNameTreeFuncDef,  pFontNameBase, MAX_FONT_NAME_LEN );
   strncpy( fontNameTreeFuncCall, pFontNameBase, MAX_FONT_NAME_LEN );
   return;
}

/*****************************************************************************
 * Set sensitivity of the module and func trees.
 *****************************************************************************/
void
SetSensitivityModuleFuncTrees( gboolean sensitive,
                               gboolean hideIfInsensitive )
{
//   return; // do nothing for test
   if ( pWidgetFuncTreeRoot )
   {
		return;
//      gtk_widget_set_sensitive( GTK_WIDGET(pWidgetFuncTreeRoot), sensitive );
/*
      if ( hideIfInsensitive == HIDE_IF_INSENSITIVE )
      {
         if ( sensitive )
            gtk_widget_show( pWidgetFuncTreeRoot );
         else
            gtk_widget_hide( pWidgetFuncTreeRoot );
      }
*/     
   }

   if ( pWidgetModuleTree )
   {
      return;
      //gtk_widget_set_sensitive( GTK_WIDGET(pWidgetModuleTree), sensitive );
/*
      if ( hideIfInsensitive == HIDE_IF_INSENSITIVE )
      {
         if ( sensitive )
            gtk_widget_show( pWidgetModuleTree );
         else
            gtk_widget_hide( pWidgetModuleTree );
      }
*/
   }

   return;
}
